﻿using Avalon.Core.Data;
using Avalon.Core.MVC.Attributes;
using Avalon.Core.Providers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;


namespace Avalon.Core.MVC {
    public class AvalonModelBinder : DefaultModelBinder {
        protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {
            bool result = true;

            //is required
            var isRequired = propertyDescriptor.Attributes.Contains(new RequiredAttribute());
            if (isRequired && value == null) {
                controllerContext.Controller.ViewData.ModelState.AddModelError(
                    propertyDescriptor.Name, string.Format("{0} can never be null.", propertyDescriptor.Name));
                result = false;
            }

            //string length
            StringLengthAttribute stringLength = (StringLengthAttribute)propertyDescriptor.Attributes.Cast<Attribute>().FirstOrDefault(a => a is StringLengthAttribute);
            if (stringLength != null && value != null) {
                if (value.ToString().Length < stringLength.MinimumLength) {
                    controllerContext.Controller.ViewData.ModelState.AddModelError(propertyDescriptor.Name, string.Format("{0} should be at least {1} characters long.", propertyDescriptor.Name, stringLength.MinimumLength));
                }

                if (value.ToString().Length > stringLength.MaximumLength) {
                    controllerContext.Controller.ViewData.ModelState.AddModelError(propertyDescriptor.Name, string.Format("{0} should not exceed {1} characters.", propertyDescriptor.Name, stringLength.MaximumLength));
                }
            }

            //is unique
            //ToDo: handle unicity

            return result;
        }

        protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value) {
            base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value);
            if (!bindingContext.ModelState.IsValid) System.Diagnostics.Debugger.Break();
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {

            // Teset to see if the current property is a parent property. If so, this value needs to be set.
            AttributeCollection attributes = propertyDescriptor.Attributes;
            foreach (Attribute attribute in attributes) {
                if (attribute is ParentAttribute) {
                    //If the child entity has a parent property this means that two extra fields should be added to the 
                    //form data; the parentId and the parentEntityName
                    Guid parentId = Guid.Parse(controllerContext.HttpContext.Request.Form["parentId"]);
                    string parentName = controllerContext.HttpContext.Request.Form["parentName"];

                    IEntityProvider entityProvider = ProviderContainer.Current.EntityProvider;
                    IEntityDescriptor entityDescriptor = entityProvider.GetEntityDescriptor(parentName);
                    IRepository repository = RepositoryConfig.Initialize(entityDescriptor.EntityType);

                    var parent = repository.Get(parentId);
                    propertyDescriptor.SetValue(bindingContext.Model, parent);
                }
            }

            if (propertyDescriptor.PropertyType == typeof(DateTime) ||
                propertyDescriptor.PropertyType == typeof(DateTime?)) {

                string tryDateTime = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name).AttemptedValue;
                DateTime temp;
                if (DateTime.TryParse(tryDateTime.ToString(), out temp)) propertyDescriptor.SetValue(bindingContext.Model, temp);
                else propertyDescriptor.SetValue(bindingContext.Model, null);
            }
            else if (propertyDescriptor.PropertyType.IsGenericType && propertyDescriptor.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) {
            }
            else {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }

        public static ModelBindingContext CreateBindingContext<TModel>(TModel model) {
            //Create the parameters for the model binder. The model binding context is the real dragon in this code.
            var valueProvider = new NameValueCollectionValueProvider(System.Web.HttpContext.Current.Request.Params, null);
            var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel));
            var modelBindingContext = new ModelBindingContext();
            modelBindingContext.ModelMetadata = modelMetadata;
            modelBindingContext.ModelState = new ModelStateDictionary();
            modelBindingContext.ValueProvider = valueProvider;

            return modelBindingContext;
        }
    }
}
